﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;

namespace ROrganizer.Client.Files
{
    public class FileProcessor
    {
        private readonly HashFacility hFacility = new HashFacility();
        private readonly HashRepository hRepository = new HashRepository();
        private readonly HashGateway hGateway = new HashGateway();

        private readonly MetaFacility mFacility = new MetaFacility();
        private readonly MetaRepository mRepository = new MetaRepository();

        private readonly ImdbRepository iRepository = new ImdbRepository();
        private readonly ImdbGateway iGateway = new ImdbGateway();

        private readonly NfoFacility nFacility = new NfoFacility();

        public event EventHandler<CompletedEventArgs> Completed;
        private void OnCompleted(string path, CollectDataMode operation)
        {
            if (this.Completed != null)
                this.Completed(this, new CompletedEventArgs(path, operation));
        }

        public void CollectData(string file, CollectDataMode mode = CollectDataMode.All)
        {
            var current = this.hRepository.Get(file);
            var meta = this.mRepository.Get(file);
            var hashed = false;
            var linked = false;

            if (mode.HasFlag(CollectDataMode.Hash) && (current == null || !current.IsHashed()))
            {
                hashed = true;
                current = this.hFacility.ComputeHash(file);
                current.Info.Link = String.Empty;
                current.Info.LinkTimestamp = null;

                this.hRepository.SaveOrUpdate(current);
                this.OnCompleted(file, CollectDataMode.Hash);
            }

            if (mode.HasFlag(CollectDataMode.Meta) && meta == null)
            {
                meta = this.mFacility.AnalyzeFile(file);

                if (meta != null)
                    this.mRepository.SaveOrUpdate(meta);

                this.OnCompleted(file, CollectDataMode.Meta);
            }

            if (mode.HasFlag(CollectDataMode.Info) && !current.IsLinked())
            {
                linked = true;
                var retrieved = this.hGateway.Query(current);

                if (retrieved != null)
                {
                    current.Info = retrieved;
                    this.hRepository.SaveOrUpdate(current);
                }

                this.OnCompleted(file, CollectDataMode.Info);
            }

            if (mode.HasFlag(CollectDataMode.Info) && !current.IsLinked() && nFacility.HasNfo(current))
            {
                linked = true;
                var nfo = nFacility.GetNfo(current);
                var link = nfo.Info.ImdbId;

                if (!String.IsNullOrEmpty(link))
                {
                    current.Info.Link = link;
                    current.Info.LinkTimestamp = DateTime.Now;
                    current.IsChanged = true;

                    this.hRepository.SaveOrUpdate(current);
                    this.OnCompleted(file, CollectDataMode.Nfo);
                }
            }

            if (!current.IsLinked())
                return;

            if (mode.HasFlag(CollectDataMode.Imdb))
            {
                var imdb = this.iRepository.Get(current.File);
                if (imdb == null || hashed || linked || !imdb.Info.Rating.HasValue)
                {
                    imdb = iGateway.Query(current);

                    if (imdb.Info != null)
                        this.iRepository.SaveOrUpdate(imdb);

                    this.OnCompleted(file, CollectDataMode.Imdb);
                }
            }
        }

        public CopyTask[] PrepareToCopy(string[] files, CopySettings settings)
        {
            var query = from file in files
                        orderby file ascending
                        select new
                        {
                            Path = file,
                            Name = this.GetName(file, rename: settings.RenameUsingImdbTitles),
                        };

            var groups = from entry in query
                         group entry by entry.Name into temp
                         from info in temp
                         let name = temp.Count() == 1 ? info.Name : info.Name + " Part " + (temp.ToList().IndexOf(info) + 1)
                         let destination = String.IsNullOrEmpty(settings.DestinationFolder) ? String.Empty : Path.Combine(settings.DestinationFolder, name) + Path.GetExtension(info.Path)
                         select new CopyTask
                         {
                             Source = info.Path,
                             Destination = destination,
                             DestinationName = name,
                             IsConflicted = !String.IsNullOrEmpty(destination) && (File.Exists(destination) || Directory.Exists(destination))
                         };

            return groups.ToArray();
        }

        public RemoveTask[] PrepareToRemove(string[] paths, RemoveSettings settings)
        {
            var query = from path in paths
                        orderby path ascending
                        select new
                        {
                            Path = path,
                            Name = this.GetName(path),
                        };

            var groups = from entry in query
                         group entry by entry.Name into temp
                         select new RemoveTask
                         {
                             Name = temp.Key,
                             Files = temp.Select(x => x.Path).ToArray(),
                             RelatedFiles = settings.RemoveAssociatedFiles ? this.GetRelatedFiles(temp.Select(x => x.Path)) : new string[] { },
                         };

            return groups.ToArray();
        }

        private string[] GetRelatedFiles(IEnumerable<string> paths)
        {
            var result = new List<string>();

            foreach (var path in paths)
            {
                var file = Path.GetFileNameWithoutExtension(path);
                var directory = Path.GetDirectoryName(path);
                var extra = Directory.GetFiles(directory, file + ".*", SearchOption.TopDirectoryOnly);

                result.AddRange(extra.Select(x => x.ToLower()));
            }

            return result.Except(paths).ToArray();
        }

        private string GetName(string file, bool rename = true)
        {
            if (!rename)
                return Path.GetFileNameWithoutExtension(file);

            var imdb = this.iRepository.Get(file);
            if (imdb == null)
                return Path.GetFileNameWithoutExtension(file);

            return FileProcessor.EspaceTitle(imdb.Info.Title);
        }

        public static string EspaceTitle(string title)
        {
            title = title.Replace("&", "and");
            var invalid = new[] { '\\', '/', ':', '*', '?', '<', '>', '|', '"', '[', ']' };

            return new String(title.Where(c => !invalid.Contains(c)).ToArray());
        }

        public void PerformCopy(CopyTask task, int buffersize = 10 * 1024 * 1024, Action<long> notifier = null)
        {
            var exists = File.Exists(task.Destination);

            try
            {
                using (var reader = new FileStream(task.Source, FileMode.Open, FileAccess.Read, FileShare.Read, buffersize))
                using (var writer = new FileStream(task.Destination, FileMode.CreateNew, FileAccess.Write, FileShare.None, buffersize))
                {
                    var buffer = new byte[buffersize];
                    var left = FileEnumerator.GetFileSize(task.Source);

                    while (left > 0)
                    {
                        var read = reader.Read(buffer, 0, buffersize);

                        writer.Write(buffer, 0, read);
                        left -= read;

                        if (notifier != null)
                            notifier(read);
                    }

                    writer.Flush();
                    writer.Close();
                }
            }
            catch
            {
                if (!exists && File.Exists(task.Destination))
                    File.Delete(task.Destination);

                throw;
            }
            finally
            {
                GC.Collect();
            }
        }

        public void PerformRemove(RemoveTask task, Action<string> notifier = null)
        {
            var query = from file in task.Files.Union(task.RelatedFiles)
                        select file;

            foreach (var file in query)
            {
                File.Delete(file);

                if (notifier != null)
                    notifier(file);
            }
        }

        [Flags]
        public enum CollectDataMode
        {
            None = 0,
            Hash = 1,
            Meta = 2,
            Info = 4,
            Imdb = 8,
            Nfo = 16,
            All = Hash | Meta | Info | Imdb
        }
       
        public class CompletedEventArgs : EventArgs
        {
            public readonly CollectDataMode Operation;
            public readonly string Path;

            public CompletedEventArgs(string path, CollectDataMode operation)
            {
                this.Path = path;
                this.Operation = operation;
            }
        }
    }
}
